/*
 * Copyright (c) 2005, 2014 vacoor
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 */
package org.vacoor.nothing.web.controller;

import com.google.common.collect.ImmutableMap;
import org.apache.commons.io.FilenameUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.multipart.MultipartFile;
import org.vacoor.nothing.common.util.IOUtils;
import org.vacoor.nothing.common.util.StringUtils;
import org.vacoor.nothing.web.service.FileStorageService;
import org.vacoor.nothing.web.service.ImageStorageService;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

/**
 * 通用的上传和下载
 * 文件 (主要用于处理不需要特殊处理的文件)
 *   文件上传 /storage/files/ul  文件字段名为 file
 *   文件访问 /storage/files/标识 (标识为上传时返回的path字段)
 *   文件下载 /storage/files/dl/标识
 * 图片 (主要用于处理需要生成不同尺寸缩略图)
 *   图片上传 /storage/images/ul
 *   图片访问 /storage/images/标识
 *
 * TODO 增加上传/下载 Token
 *
 * @author vacoor
 */
@Controller
@RequestMapping("/storage")
public class StorageController implements ServletContextAware {
    public static final String DEFAULT_FILE_MIME_TYPE = "application/octet-stream";
    public static final String DEFAULT_IMAGE_MIME_TYPE = "image/jpeg";

    private ServletContext servletContext;
    @Autowired
    private FileStorageService fileStorageService;
    @Autowired
    private ImageStorageService imageStorageService;

    /**
     * 一个最简单的文件上传
     * 上传文件字段名称为 file
     */
    @ResponseBody
    @RequestMapping(value = "files/ul", method = RequestMethod.POST)
    public Map<String, Object> upload(@RequestPart("file") MultipartFile multipart, HttpServletRequest request) throws IOException {
        if (multipart == null || multipart.isEmpty()) {
            return ImmutableMap.<String, Object>of("error", 1, "message", "Invalid file, must not be empty");
        }

        String name = multipart.getOriginalFilename();
        String suffix = FilenameUtils.getExtension(name);
        InputStream inputStream = multipart.getInputStream();
        // 存储后标示不包含后缀, 为了方便访问时获取 mime 类型这里加上后缀
        String path = fileStorageService.store(inputStream) + "." + suffix;
        String url = request.getContextPath() + "/storage/files/" + path;

        // return ImmutableMap.<String, Object>of("error", 0, "size", size, "url", name);
        return ImmutableMap.<String, Object>of("error", 0, "path", path, "url", url);
    }

    /**
     * 获取资源以内联方式返回
     * url:
     * 1. hash
     * 2. hash.png
     */
    @RequestMapping("files/{fid:.*}") // spring 默认会去掉后缀, 使用 .* 避免
    public void inline(@PathVariable("fid") String fid, HttpServletResponse response) throws IOException {
        String id = FilenameUtils.removeExtension(fid);

        InputStream is = fileStorageService.readAsStream(id);
        if (null != is) {
            String mimeType = getMimeType(fid);
            if (StringUtils.hasText(mimeType)) {
                response.setContentType(mimeType);
            }

            response.setHeader("Content-Disposition", "inline");
            IOUtils.flow(is, response.getOutputStream(), true, true);
        } else {
            // writer.write("您下载的文件不存在")
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "File Not Find");
        }
    }

    /**
     * download
     * url:
     * 1. dl/hash?filename=test.zip
     * 2. dl/hash?filename=test.zip&responseContentType=application/zip
     * 另外一种常见的形式是 将文件名作为url一部分
     * dl/hash/filename?contentType=application/zip  好处是当不指定 Content-Disposition 中filename时以URL作为文件名, 可以避免文件名乱码
     * Amazon S3 是通过 response-content-disposition 参数来指定完整的 Content-Disposition
     */
    @RequestMapping("files/dl/{fid:.*}")
    // @RequestMapping("dl/{hash}/{filename}")
    public void download(@PathVariable("fid") String fid, String filename, String responseContentType, HttpServletResponse response) throws IOException {
        String id = FilenameUtils.removeExtension(fid);

        InputStream is = fileStorageService.readAsStream(id);
        if (null != is) {
            filename = StringUtils.hasText(filename) ? filename : fid;
            response.setHeader("Content-Disposition", "attachment; filename=" + new String(filename.getBytes("ISO8859-1"), "UTF-8"));
            // response.setHeader("Content-MD5", hash);

            // 如果 contentType 为空, 尝试从 filename 中获取
            if (!StringUtils.hasText(responseContentType)) {
                responseContentType = getMimeType(filename);
            }

            // 如果 contentType 仍然为空, 尝试从标识中获取
            if (!StringUtils.hasText(responseContentType)) {
                responseContentType = getMimeType(fid);
            }

            responseContentType = StringUtils.hasText(responseContentType) ? responseContentType : DEFAULT_FILE_MIME_TYPE;
            response.setContentType(responseContentType);

            IOUtils.flow(is, response.getOutputStream(), true, true);
        } else {
            response.sendError(HttpServletResponse.SC_NOT_FOUND, "File Not Find");
        }
    }

    /* *****************************
     *
     *         images storage
     *
     * *****************************/

    /**
     * 用于需要生成缩略图的图片存储
     * 上传图片字段名称为 img
     */
    @ResponseBody
    @RequestMapping(value = "images/ul", method = RequestMethod.POST)
    public Map<String, Object> uploadImage(@RequestPart("img") MultipartFile multipart, HttpServletRequest request) throws IOException {
        if (multipart == null || multipart.isEmpty()) {
            return ImmutableMap.<String, Object>of("error", 1, "message", "Invalid file, must not be empty");
        }

        InputStream inputStream = multipart.getInputStream();
        String path = imageStorageService.store(inputStream);
        String url = request.getContextPath() + "/storage/images/" + path;

        if (StringUtils.hasLength(path)) {
            return ImmutableMap.<String, Object>of("error", 0, "path", path, "url", url);
        } else {
            return ImmutableMap.<String, Object>of("error", 1, "message", "upload failure");
        }
    }

    /**
     * 获取图片资源
     */
    @RequestMapping("images/{path:.*}")
    public void view(@PathVariable("path") String path, HttpServletResponse response) throws IOException {
        InputStream is = imageStorageService.readAsStream(path);

        if (null != is) {
            String type = getMimeType(path);
            type = StringUtils.hasText(type) ? type : DEFAULT_IMAGE_MIME_TYPE;

            response.setContentType(type);
            response.setHeader("Content-Disposition", "inline");
            IOUtils.flow(is, response.getOutputStream(), true, true);
        }
    }

    protected String getMimeType(String filename) {
        return servletContext.getMimeType(filename);
    }

    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }
}
